#include <iostream>
#include <string>
#include <ctime>
#include <cstring>
#include <pthread.h>
#include <unistd.h>

using namespace std;
// 细节：
// 1. 凡是访问同一个临界资源的线程，都要进行加锁保护，而且必须加同一把锁，这个是一个游戏规则，不能有例外
// 2. 每一个线程访问临界区之前，得加锁，加锁本质是给 临界区 加锁，加锁的粒度尽量要细一些
// 3. 线程访问临界区的时候，需要先加锁->所有线程都必须要先看到同一把锁->锁本身就是公共资源->锁如何保证自己的安全？-> 加锁和解锁本身就是原子的！
// 4. 临界区可以是一行代码，可以是一批代码，a. 线程可能被切换吗？当然可能， 不要特殊化加锁和解锁，还有临界区代码。
//    b. 切换会有影响吗？不会，因为在我不在期间，任何人都没有办法进入临界区，因为他无法成功的申请到锁！因为锁被我拿走了！
// 5. 这也正是体现互斥带来的串行化的表现，站在其他线程的角度，对其他线程有意义的状态就是：锁被我申请(持有锁)，锁被我释放了(不持有锁)， 原子性就体现在这里
// 6. 解锁的过程也被设计成为原子的！
// 7. 锁 的 原理的理解
int g_ticket = 1000;

class ThreadData
{
public:
    ThreadData(const string &name, pthread_mutex_t *pmutex)
        : _name(name),
          _pmutex(pmutex)
    {
    }

public:
    string _name;
    pthread_mutex_t *_pmutex;
};

void *buyTicket(void *arg)
{
    ThreadData *td = static_cast<ThreadData *>(arg);
    while (true)
    {
        // 进入临界区前加锁
        pthread_mutex_lock(td->_pmutex);
        if (g_ticket > 0)
        {
            // 抢票ing...
            usleep(1000); // 模拟抢票所花的时间
            cout << td->_name;
            cout << " Get a ticket. Residue: [" << g_ticket-- << "]\n";

            // done, 解锁
            pthread_mutex_unlock(td->_pmutex);
        }
        else
        {
            // 注意这里也要!
            pthread_mutex_unlock(td->_pmutex);
            break;
        }
    }
    return nullptr;
}

int main()
{
    // 声明一个锁
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);

    pthread_t tids[4];
    for (int i = 0; i < 4; ++i)
    {
        char name[64];
        snprintf(name, sizeof(name), "thread_%d", i + 1);
        ThreadData *td = new ThreadData(name, &mutex);
        pthread_create(tids + i, nullptr, buyTicket, td);
    }
    for (int i = 0; i < 4; ++i)
        pthread_join(tids[i], nullptr);

    cout << "Finally, the rest of ticket :[" << g_ticket << "]\n";

    pthread_mutex_destroy(&mutex);
    return 0;
}